﻿using System;
using System.Windows.Forms;
using Simple3DRedactor.Domain;
using Tao.OpenGl;

namespace Simple3DRedactor.OpenGLViewer
{
    abstract class Renderer
    {
        protected Renderer(OpenGLViewer viewer)
        {
            Viewer = viewer;
        }

        public OpenGLViewer Viewer { get; private set; }

        public event Action<IOpenGLObjectView> SelectionChanged;
        private void OnSelectionChanged()
        {
            if (SelectionChanged != null) SelectionChanged(SelectedObject);
        }

        private IOpenGLObjectView _selectedObject;
        public IOpenGLObjectView SelectedObject
        {
            get { return _selectedObject; }
            protected set
            {
                _selectedObject = value;
                if (_selectedObject != null) _selectedObject.Selected = true;
                OnSelectionChanged();
            }
        }

        protected void ClearSelection()
        {
            if (Viewer.SceneView != null)
                foreach (var view in Viewer.SceneView)
                    view.Selected = false;

            SelectedObject = null;
        }

        private const int BufSize = 512;
        private readonly int[] _selectBuffer = new int[BufSize];

        protected bool TrySelectObject(int x, int y)
        {
            if (Viewer.Scene == null)
                return false;

            var viewport = new int[4];
            Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewport);

            Gl.glSelectBuffer(_selectBuffer.Length, _selectBuffer);
            Gl.glRenderMode(Gl.GL_SELECT);
            Gl.glInitNames();
            Gl.glPushName(0);

            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();
            Glu.gluPickMatrix(x, viewport[3] - y, 10.0, 10.0, viewport);
            LoadProjectionMatrix(viewport[2], viewport[3]);

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
            LoadModelViewMatrix();

            RenderScene(Viewer.SceneView);

            var hits = Gl.glRenderMode(Gl.GL_RENDER);

            for (int i = 0, ind = 0; i < hits; ++i)
            {
                var names = _selectBuffer[ind++];
                ind++; //var z1 = (double)_selectBuffer[ind++] / 0x7fffffff;
                ind++; //var z2 = (double)_selectBuffer[ind++] / 0x7fffffff;
                for (int j = 0; j < names; ++j)
                {
                    var name = _selectBuffer[ind++];
                    var node = Viewer.ViewFactory.GetViewById(name);

                    if (node != null)
                    {
                        SelectedObject = node;
                        return true;
                    }
                }
            }

            return false;
        }

        public void SetupCamera(int width, int height)
        {
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();
            LoadProjectionMatrix(width, height);

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
            LoadModelViewMatrix();
        }

        public abstract void LoadProjectionMatrix(int width, int height);
        public abstract void LoadModelViewMatrix();

        public abstract void Show(SceneView<IOpenGLObjectView> scene);
        
        public virtual void OnMouseDown(MouseEventArgs e)
        {
        }

        public virtual void OnMouseMove(MouseEventArgs e)
        {
        }

        public virtual void OnMouseUp(MouseEventArgs e)
        {
        }

        public virtual void OnMouseWheel(MouseEventArgs e)
        {
        }

        public virtual void OnKeyPress(KeyPressEventArgs e)
        {
        }

        public virtual void OnKeyDown(KeyEventArgs e)
        {
        }

        protected static void RenderScene(SceneView<IOpenGLObjectView> scene)
        {
            if (scene != null)
            {
                foreach (var view in scene)
                    view.Setup();

                foreach (var view in scene)
                    view.Render();
            }
        }
    }
}
